%require "2.4.1"
%skeleton "lalr1.cc"

%defines
%define namespace "LALG"
%define parser_class_name "Parser"

%locations
%initial-action
{
    @$.begin.filename = @$.end.filename = &driver.filename;
};

%parse-param { class Driver& driver }
%lex-param   { Driver& driver }

%error-verbose

%code requires
{
#include "Parser.h"
#include "SymbolsTable.h"
}

%code provides
{
    /*-----------------------------------------------------------------------------
     *  Chama o scanner para recuperar próximo token
     *-----------------------------------------------------------------------------*/
    int yylex(LALG::Parser::semantic_type *yylval,
            LALG::Parser::location_type *yyloc, LALG::Driver &driver);
}

%union
{
    string* sval;
    int ival;
    float fval;

    VarValue var;
    VarList* list;
    ExpressionTree* exp;
}

/*-----------------------------------------------------------------------------
 *  Token usado para verificar fim de arquivo
 *-----------------------------------------------------------------------------*/
%token T_EOF

/*-----------------------------------------------------------------------------
 *  Tokens para números e ids
 *-----------------------------------------------------------------------------*/
%token <sval> T_ID         "identifier"
%token <ival> T_NINTEGER   "integer number"
%token <fval> T_NREAL      "real number"

/*-----------------------------------------------------------------------------
 *  Tokens para símbolos reservados
 *-----------------------------------------------------------------------------*/
%token T_SEMICOLON   ";"
%token T_DOT         "."
%token T_EQUAL       "="
%token T_COLON       ":"
%token T_COMMA       ","
%token T_ASSIGN      ":="
%token T_DIFF        "<>"
%token T_GRTR_EQ     ">="
%token T_LSSR_EQ     "<="
%token T_GREATER     ">"
%token T_LESSER      "<"
%token T_PLUS        "+"
%token T_MINUS       "-"
%token T_TIMES       "*"
%token T_DIVISION    "/"
%token T_LEFT_PAREN  "("
%token T_RIGHT_PAREN ")"

/*-----------------------------------------------------------------------------
 *  Tokens para palavras reservadas
 *-----------------------------------------------------------------------------*/
%token T_PROGRAM    "word program"
%token T_BEGIN      "word begin"
%token T_END        "word end"
%token T_CONST      "word const"
%token T_VAR        "word var"
%token T_REAL       "word real"
%token T_INTEGER    "word integer"
%token T_PROCEDURE  "word procedure"
%token T_ELSE       "word else"
%token T_READLN     "word readln"
%token T_WRITELN    "word writeln"
%token T_REPEAT     "word repeat"
%token T_UNTIL      "word until"
%token T_IF         "word if"
%token T_THEN       "word then"
%token T_WHILE      "word while"
%token T_DO         "word do"

/*-----------------------------------------------------------------------------
 *  Tokens para erros
 *-----------------------------------------------------------------------------*/
%token T_UNKNOWN     // Símbolo não permitido
%token T_MF_INTEGER  // Número inteiro mal formado
%token T_MF_REAL     // Número real mal formado
%token T_MF_ID       // Identificador mal formado
%token T_LONG_ID     // Identificador muito longo

/*-----------------------------------------------------------------------------
 *  Tipos para os não terminais
 *-----------------------------------------------------------------------------*/
%type <var>  dc_c1
%type <var>  dc_c2
%type <var>  tipo_var
%type <list> variaveis
%type <list> mais_var
%type <list> parametros
%type <list> lista_par
%type <list> mais_par
%type <list> lista_arg
%type <list> argumentos
%type <list> mais_ident
%type <list> cmd_param

%type <exp>  condicao
%type <exp>  expressao
%type <exp>  relacao
%type <exp>  op_un
%type <exp>  op_ad
%type <exp>  op_mul
%type <exp>  termo
%type <exp>  outros_termos
%type <exp>  fator
%type <exp>  fator_exp
%type <exp>  mais_fatores

%type <var>  numero

%destructor { delete $$; } <sval>
%destructor { delete $$; } <list>

%%

/*-----------------------------------------------------------------------------
 *  Regras de programa
 *-----------------------------------------------------------------------------*/
programa 
    : T_PROGRAM programa1
    | error T_SEMICOLON { errorLet(SIN_PROGRAM_EXPECTED, SIN_SEMICOLON); } corpo programa3
    | error T_DOT { errorLet(SIN_PROGRAM_EXPECTED, SIN_DOT); YYABORT; }
    ;

programa1
    : T_ID { addProgram($1, driver); } programa2
    | error T_SEMICOLON { errorLet(SIN_ID_EXPECTED, SIN_SEMICOLON); } corpo programa3
    | error T_DOT { errorLet(SIN_ID_EXPECTED, SIN_DOT); YYABORT; }
    ;
    
programa2
    : T_SEMICOLON corpo programa3
    | error T_DOT { errorLet(SIN_SEMICOLON_EXPECTED, SIN_DOT); YYABORT; }
    ;
    
programa3
    : T_DOT
    | error { errorLet(SIN_DOT_EXPECTED, 0); YYABORT; }
    ;

/*-----------------------------------------------------------------------------
 *  Regras de corpo
 *-----------------------------------------------------------------------------*/    
corpo
    : dc T_BEGIN comandos corpo1
    | dc error T_END { errorLet(SIN_BEGIN_EXPECTED, SIN_END); }
    ;
    
corpo1
    : T_END
    ;

/*-----------------------------------------------------------------------------
 *  Regras de declaração
 *-----------------------------------------------------------------------------*/
dc
    : dc_c dc_v dc_p
    ;

/*-----------------------------------------------------------------------------
 *  Regras de declaração de constante
 *-----------------------------------------------------------------------------*/
dc_c
    : dc_c0 dc_c
    |
    ;

dc_c0
    : T_CONST { markLine; } T_ID dc_c1 { addConstant($3, $4, driver); }
    | T_CONST error T_EQUAL { errorLet(SIN_ID_EXPECTED, SIN_EQUAL); } numero T_SEMICOLON
    | T_CONST error T_SEMICOLON { errorLet(SIN_ID_EXPECTED, SIN_SEMICOLON); }
    ;

dc_c1
    : T_EQUAL dc_c2 { $$ = $2; }
    | error T_SEMICOLON { errorLet(SIN_EQUAL_EXPECTED, SIN_SEMICOLON); $$.error = true; }
    ;
    
dc_c2
    : numero dc_c3 { $$ = $1; $$.error = false; }
    | error T_SEMICOLON { errorLet(SIN_NUMBER_EXPECTED, SIN_SEMICOLON); $$.error = true; }
    ;
    
dc_c3
    : T_SEMICOLON
    | error { errorLet(SIN_SEMICOLON_EXPECTED, 0); }
    ;

/*-----------------------------------------------------------------------------
 *  Regras de declaração de variável
 *-----------------------------------------------------------------------------*/
dc_v
    : dc_v0 dc_v
    |
    ;

dc_v0
    : T_VAR variaveis { markLine; } T_COLON tipo_var dc_v1 { addVariable($2, $5, driver); }
    | T_VAR variaveis error T_SEMICOLON { errorLet(SIN_COLON_EXPECTED, SIN_SEMICOLON); }
    ;
    
dc_v1
    : T_SEMICOLON
    | error { errorLet(SIN_SEMICOLON_EXPECTED, 0); }
    ;

/*-----------------------------------------------------------------------------
 *  Regras de tipo de variável
 *-----------------------------------------------------------------------------*/
tipo_var
    : T_REAL { $$.type = FLOAT; $$.error = false; }
    | T_INTEGER { $$.type = INTEGER; $$.error = false; }
    | error { errorSkip(SIN_TYPE_EXPECTED, 0); $$.error = true; }
    ;

/*-----------------------------------------------------------------------------
 *  Regras de variáveis
 *-----------------------------------------------------------------------------*/
variaveis
    : T_ID mais_var { $$ = new VarList($2->begin(), $2->end()); $$->push_front(VarArg{*$1, 0}); }
    | error { errorSkip(SIN_ID_EXPECTED, 0); } mais_var { $$ = new VarList($3->begin(), $3->end()); }
    ;

/*-----------------------------------------------------------------------------
 *  Regras de mais variáveis
 *-----------------------------------------------------------------------------*/
mais_var
    : T_COMMA variaveis { $$ = new VarList($2->begin(), $2->end()); }
    | { $$ = new VarList; }
    ;

/*-----------------------------------------------------------------------------
 *  Regras de declaração de procedimento
 *-----------------------------------------------------------------------------*/
dc_p
    : dc_p0 { endProcedure(driver); } dc_p
    |
    ;

dc_p0
    : T_PROCEDURE { markLine; } T_ID parametros { startProcedure($3, $4, driver); } dc_p1
    | T_PROCEDURE error T_SEMICOLON { startProcedure(NULL, NULL, driver); errorLet(SIN_ID_EXPECTED, SIN_SEMICOLON); } corpo_p
    ;
   
dc_p1
    : T_SEMICOLON corpo_p
    | error { errorLet(SIN_SEMICOLON_EXPECTED, 0); } corpo_p
    ; 

/*-----------------------------------------------------------------------------
 *  Regras de parâmetros
 *-----------------------------------------------------------------------------*/
parametros
    : T_LEFT_PAREN lista_par T_RIGHT_PAREN { $$ = new VarList($2->begin(), $2->end()); }
    | T_LEFT_PAREN lista_par error { errorSkip(SIN_RPAREN_EXPECTED, 0); $$ = new VarList($2->begin(), $2->end()); }
    | { $$ = new VarList; }
    ;

/*-----------------------------------------------------------------------------
 *  Regras de lista de parâmetros
 *-----------------------------------------------------------------------------*/
lista_par
    : variaveis T_COLON tipo_var mais_par {
        $$ = new VarList($4->begin(), $4->end());
        for(VarArg arg : *$1) $$->push_back(VarArg{arg.name, $3.type}); }
    | variaveis error { errorLet(SIN_COLON_EXPECTED, 0); } tipo_var mais_par {
        $$ = new VarList($5->begin(), $5->end());
        for(VarArg arg : *$1) $$->push_back(VarArg{arg.name, $4.type}); }
    ;
    
/*-----------------------------------------------------------------------------
 *  Regras de mais parâmetros
 *-----------------------------------------------------------------------------*/
mais_par
    : T_SEMICOLON lista_par { $$ = new VarList($2->begin(), $2->end()); }
    | { $$ = new VarList; }
    ;

/*-----------------------------------------------------------------------------
 *  Regras de corpo do procedimento
 *-----------------------------------------------------------------------------*/
corpo_p
    : dc_loc T_BEGIN comandos corpo_p1
    | dc_loc error T_END { errorLet(SIN_BEGIN_EXPECTED, SIN_END); } corpo_p2
    | dc_loc error T_SEMICOLON { errorLet(SIN_BEGIN_EXPECTED, SIN_SEMICOLON); }
    ;
    
corpo_p1
    : T_END corpo_p2
    | error T_SEMICOLON { errorLet(SIN_END_EXPECTED, SIN_SEMICOLON); }
    ;
    
corpo_p2
    : T_SEMICOLON
    | error { errorLet(SIN_SEMICOLON_EXPECTED, 0); }
    ;

/*-----------------------------------------------------------------------------
 *  Regras de declaração de variáveis locais
 *-----------------------------------------------------------------------------*/
dc_loc
    : dc_v
    ;

/*-----------------------------------------------------------------------------
 *  Regras de lista de argumentos
 *-----------------------------------------------------------------------------*/
lista_arg
    : T_LEFT_PAREN argumentos T_RIGHT_PAREN  { $$ = new VarList($2->begin(), $2->end()); }
    | T_LEFT_PAREN argumentos error { errorLet(SIN_RPAREN_EXPECTED, 0); $$ = new VarList($2->begin(), $2->end()); }
    | { $$ = new VarList; }
    ;
    
/*-----------------------------------------------------------------------------
 *  Regras de argumentos
 *-----------------------------------------------------------------------------*/
argumentos
    : T_ID mais_ident { $$ = new VarList($2->begin(), $2->end()); $$->push_front(VarArg{*$1, 0}); }
    | error { errorSkip(SIN_ID_EXPECTED, 0); } mais_ident { $$ = new VarList($3->begin(), $3->end()); }
    ;
    
/*-----------------------------------------------------------------------------
 *  Regras de mais argumentos
 *-----------------------------------------------------------------------------*/
mais_ident
    : T_SEMICOLON argumentos { $$ = new VarList($2->begin(), $2->end()); }
    | { $$ = new VarList; }
    ;
    
/*-----------------------------------------------------------------------------
 *  Regras de condição falsa
 *-----------------------------------------------------------------------------*/
pfalsa
    : T_ELSE { startElse(driver); } cmd
    | 
    ;

/*-----------------------------------------------------------------------------
 *  Regras de comandos falsa
 *-----------------------------------------------------------------------------*/
comandos
    : cmd T_SEMICOLON comandos
    | cmd error { errorLet(SIN_SEMICOLON_EXPECTED, 0); } comandos
    | error T_SEMICOLON { errorLet(SIN_INVALID_CMD, SIN_SEMICOLON); } comandos
    | 
    ;
  
/*-----------------------------------------------------------------------------
 *  Regras de comando
 *-----------------------------------------------------------------------------*/
cmd
    : T_READLN { markLine; } cmd_param { checkLineCall(READLN, $3, driver); }
    | T_WRITELN { markLine; } cmd_param { checkLineCall(WRITELN, $3, driver); }
    | T_REPEAT { markLine; startRepeat(driver); } cmd_repeat
    | T_IF { markLine; } cmd_if
    | T_ID { markLine; } T_ASSIGN expressao { checkAttribution($1, $4, driver); delete $4; }
    | T_ID { markLine; } lista_arg { checkProcedureCall($1, $3, driver); }
    | T_BEGIN cmd_begin
    | T_WHILE { markLine; } cmd_while
    ;
    
/*-----------------------------------------------------------------------------
 *  Regras de comandos readln e writeln
 *-----------------------------------------------------------------------------*/
cmd_param
    : T_LEFT_PAREN variaveis T_RIGHT_PAREN { $$ = new VarList($2->begin(), $2->end()); }
    ;
    
/*-----------------------------------------------------------------------------
 *  Regras de comando repeat
 *-----------------------------------------------------------------------------*/
cmd_repeat
    : comandos T_UNTIL condicao { endRepeat($3, driver); delete $3; }
    ;
    
/*-----------------------------------------------------------------------------
 *  Regras de comando if
 *-----------------------------------------------------------------------------*/
cmd_if
    : condicao T_THEN { startIf($1, driver); delete $1; } cmd pfalsa { endIf(driver); }
    ;
    
/*-----------------------------------------------------------------------------
 *  Regras de comando begin
 *-----------------------------------------------------------------------------*/
cmd_begin
    : comandos T_END
    ;

/*-----------------------------------------------------------------------------
 *  Regras de comando while
 *-----------------------------------------------------------------------------*/
cmd_while
    : condicao T_DO { startWhile($1, driver); delete $1; } cmd { endWhile(driver); }
    ;
    
/*-----------------------------------------------------------------------------
 *  Regras de condição
 *-----------------------------------------------------------------------------*/
condicao
    : expressao relacao expressao { $$ = $2; $$->left = $1; $$->right = $3; }
    ;

/*-----------------------------------------------------------------------------
 *  Regras de relação
 *-----------------------------------------------------------------------------*/
relacao
    : T_EQUAL { $$ = new ExpressionTree; $$->type = OPERATOR; $$->op = EQUAL; }
    | T_DIFF { $$ = new ExpressionTree; $$->type = OPERATOR; $$->op = DIFFERENT; }
    | T_GRTR_EQ { $$ = new ExpressionTree; $$->type = OPERATOR; $$->op = GTEQUAL; }
    | T_LSSR_EQ { $$ = new ExpressionTree; $$->type = OPERATOR; $$->op = LEQUAL; }
    | T_GREATER { $$ = new ExpressionTree; $$->type = OPERATOR; $$->op = GREATER; }
    | T_LESSER { $$ = new ExpressionTree; $$->type = OPERATOR; $$->op = LESSER; }
    | error { errorLet(SIN_RELATION_EXPECTED, 0); $$ = new ExpressionTree; $$->type = ERROR; }
    ;
    
/*-----------------------------------------------------------------------------
 *  Regras de expressão
 *-----------------------------------------------------------------------------*/
expressao
    : termo outros_termos {
        if($2 == NULL) $$ = $1;
        else { $$ = $2; $$->left = $1; } }
    ;
    
/*-----------------------------------------------------------------------------
 *  Regras de operador unitário (sinal)
 *-----------------------------------------------------------------------------*/
op_un
    : T_PLUS { $$ = NULL; }
    | T_MINUS { $$ = new ExpressionTree; $$->type = OPERATOR; $$->op = MINUS; }
    | { $$ = NULL; }
    ;
    
/*-----------------------------------------------------------------------------
 *  Regras de outros termos
 *-----------------------------------------------------------------------------*/
outros_termos
    : op_ad termo outros_termos {
        $$ = $1;
        if($3 == NULL) { $$->right = $2; }
        else { $3->left = $2; $$->right = $3; } }
    | { $$ = NULL; }
    ;
    
/*-----------------------------------------------------------------------------
 *  Regras de operações de soma e subtração
 *-----------------------------------------------------------------------------*/
op_ad
    : T_PLUS { $$ = new ExpressionTree; $$->type = OPERATOR; $$->op = PLUS; }
    | T_MINUS { $$ = new ExpressionTree; $$->type = OPERATOR; $$->op = MINUS; }
    ;
    
/*-----------------------------------------------------------------------------
 *  Regras de termo
 *-----------------------------------------------------------------------------*/
termo
    : op_un fator mais_fatores {
        ExpressionTree* aux;
        if($1 != NULL) { $1->left = $2; $2->right = NULL; aux = $1; }
        else { aux = $2; }
        if($3 == NULL) { $$ = aux; }
        else { $3->left = aux; $$ = $3; } }
    ;
    
/*-----------------------------------------------------------------------------
 *  Regras de mais fatores
 *-----------------------------------------------------------------------------*/
mais_fatores
    : op_mul fator mais_fatores {
        $$ = $1;
        if($3 == NULL) { $$->right = $2; }
        else { $3->left = $2; $$->right = $3; } }
    | { $$ = NULL; }
    ;
    
/*-----------------------------------------------------------------------------
 *  Regras de operações de multiplicação e divisão
 *-----------------------------------------------------------------------------*/
op_mul
    : T_TIMES { $$ = new ExpressionTree; $$->type = OPERATOR; $$->op = TIMES; }
    | T_DIVISION { $$ = new ExpressionTree; $$->type = OPERATOR; $$->op = DIVISION; }
    ;
    
/*-----------------------------------------------------------------------------
 *  Regras de fator
 *-----------------------------------------------------------------------------*/
fator
    : T_ID { $$ = new ExpressionTree; $$->type = VALUE; $$->sval = new string(*$1); }
    | numero {
        $$ = new ExpressionTree;
        if($1.type = INTEGER) { $$->type = INTEGER; $$->ival = $1.ivalue; }
        else { $$->type = FLOAT; $$->fval = $1.fvalue; } }
    | T_LEFT_PAREN fator_exp { $$ = $2; }
    | error { errorSkip(SIN_FACTOR_EXPECTED, 0); $$ = new ExpressionTree; $$->type = ERROR; }
    ;
    
fator_exp
    : expressao T_RIGHT_PAREN { $$ = $1; }
    | expressao error { errorLet(SIN_RPAREN_EXPECTED, 0); $$ = $1; }
    ;
    
/*-----------------------------------------------------------------------------
 *  Regras de número
 *-----------------------------------------------------------------------------*/
numero
    : T_NINTEGER { $$.type = INTEGER; $$.ivalue = $1; }
    | T_NREAL    { $$.type = FLOAT; $$.fvalue = $1; }
    ;

%%
